#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         EntityChooser.py
#
# Copyright:    Copyright (c) 2006, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A EntityChooser simple component to choose
#               zero, one or more entities from an associated
#               EntitySource
#
# See Also:     Component, EntitySource
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Component import Component, ComponentInfo
from Products.KnowledgeEngine.Assessible import Assessible
from Products.KnowledgeEngine.AssessmentFilter import AssessmentFilter
from Products.KnowledgeEngine.AssessmentComponentState import AssessmentComponentState
from Products.KnowledgeEngine.AssessmentComponentValueTypes import *
from Products.KnowledgeEngine.KERLProcessor import *
from Products.KnowledgeEngine.KERLExtensions import KERLExtensions
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.Graphing.Graphable import *
from Products.KnowledgeEngine.Graphing.Datum import *
from Products.KnowledgeEngine.User import User 
from Products.KnowledgeEngine.Caching import RequestCache 
from Products.KnowledgeEngine.SimpleWizard import *
from Products.KnowledgeEngine.WizardStepDefinition import *
from Products.KnowledgeEngine.WizardState import *

#####################################################################
## Zope CMF Library Imports
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore import permissions
from Products.CMFCore.permissions import ModifyPortalContent
from Products.CMFCore.utils import getToolByName

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

def getEntitySources(context):
    """
        Return the entity sources in the portal as [(Title,Identity)]
    """
    
    #get the portal catalog
    catalog = getToolByName(context, "knowledgeengine_genericobjectcatalogservice")
    
    #search for entity sources by their family
    searchResults = catalog(getClassFamilyName = "EntitySource")
    
    #results aren't the actual objects, so lets reference them
    result = []
    for searchResult in searchResults:
        #attempt to resolve the actual object reference
        entitySource = searchResult.getObject()
        
        #only add models that truely exist!
        if entitySource is not None:
            result = result + [(entitySource.getTitleOrId(), entitySource.getIdentity())]
            
    return result

#####################################################################
## EntityChooserInfo Class Definition
class EntityChooserInfo(ComponentInfo, Assessible):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Return the class name
        """
        return 'EntityChooser'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Entity Chooser"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "ec"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/EntityChooser_icon.gif', globals())

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.
            
            Override an return true if the instance is to be
            a container for other instances.
            
            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        return false          

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified
            by the className
        """

        return className == 'Assessible' or ComponentInfo.isInstanceOf(self, className)

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: nothing is permitted in an entity chooser
        return false
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """
        
        #DEFAULT RULE: entity choosers are only permitted if there are some entity sources
        return len(getEntitySources(containerInstance or self)) > 0 and ComponentInfo.isPermittedInContainer(self, containerClassInfo, containerInstance, instance)
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ComponentInfo.onConfigureProperties(self)          

        #add the 'entitySourceIdentity' property        
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="entitySourceIdentity",
                displayName="Entity Source",
                description="The source from which Entities will be chosen",
                propertyType=pt_STRING,
                propertyFormat=pf_NATIVE,
                hasEnumeratedValues=true,
                enumeratedValuesFunction=getEntitySources,
                defaultValue=None,
                constructorEVAP=evap_READWRITE,
                propertyEditorEVAP=evap_READONLY,
                isMandatory=true
            )
        )

        
        # Entity display template. Overrides entity source template.
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="entityDisplayTemplate",
                displayName="Entity Display Template",
                description="The name of the template to be used to display the entity in assessments and reports. Leave empty to use the template defined in the entity source.",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                defaultValue=None,
                constructorEVAP=evap_READWRITE,
                propertyEditorEVAP=evap_READWRITE,
            )
        )


    #---------------------------------------------------------------#

#####################################################################
## EntityChooser Class Definition
class EntityChooser(Component, KERLExtensions):
    """
        The Entity Chooser Component
    """

    #---------------------------------------------------------------#

    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = EntityChooserInfo()

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the EntityChooser"        

        Component.__init__(self, id, identity)


    #---------------------------------------------------------------#
    def getMaximumEntitiesForSimpleChooser(self):
        """
            The maximum number of entities to appear in a dropdown
            otherwise a finder is required.
        """
        
        return 100

    #---------------------------------------------------------------#

    def getEntitySource(self):
        """
            Return the entity source that provides entities 
            for this chooser.
            
            Returns None if the entity source is no longer available
        """
        
        #get the portal catalog
        catalog = getToolByName(self, "knowledgeengine_genericobjectcatalogservice")
        
        #search for the entity source identity
        searchResults = catalog(getIdentity = self.entitySourceIdentity)
        
        if len(searchResults) == 1:
            return searchResults[0].getObject()
        else:
            return None

        
    #---------------------------------------------------------------#
    def getEntityDisplayTemplate(self):
        """
            Returns the template used to display this entity. Checks
            locally first, before delegating to the EntitySource.
        """
        
        if not self.entityDisplayTemplate:
            return self.getEntitySource().getEntityDisplayTemplate()
        
        return self.entityDisplayTemplate

        
    #---------------------------------------------------------------#
    def renderEntity(self, request, **args):
        """
            Return the rendered entity
        """

        #dynamically determine the assessment editor rendering page for this object
        return eval("self.%s(self, REQUEST=request, **args)" % self.getEntityDisplayTemplate())

        
    #---------------------------------------------------------------#
    def getComponentProperty(self, identity, propertyName, assessment = None):
        """
            Returns the value of the specified property, given the
            identity of the component (which may be used to lookup states) and
            an assessment.

            RETURNS: None if the property doesn't exist
        """

        if propertyName == "value":
            return self.getEntitySource().getEntity(assessment.getComponentState(identity).getValue()).getTitle()
        else:
            return Component.getComponentProperty(self, identity, propertyName, assessment)


    #---------------------------------------------------------------#
    # BEGIN: Assessible Implementation                              #
    #---------------------------------------------------------------#
    def getAssessmentValueType(self):
        """
            ABSTRACT: Returns the type of the value that is to be stored
            for this component when it's assessment state is persisted.

            NOTE 1: See AssessmentComponentValueTypes for a list of defined contants
            NOTE 2: This method MUST be overriden in the implementing
            class to specify the correct type for value persistence

            RETURN TYPE: String
        """

        #entity chooser assessment values are a string of entity identies chosen
        return st_STRING

    #---------------------------------------------------------------#

    def getDefaultAssessmentState(self, assessment):
        """
            ABSTRACT: Constructs and returns a suitable 'default'
            AssessmentComponentState for the provided Assessment
            instance.

            NOTE: This method MUST be overriden in the implementing
            class to provide a suitable default value for the component

            RETURN TYPE: AssessmentComponentState
        """

        return AssessmentComponentState(assessment.getIdentity(), assessment.getModelIdentity(), self.getIdentity(), self.getAssessmentValueType(), "", revision=assessment.getRevision())

    #---------------------------------------------------------------#

    def updateAssessmentState(self, assessment, formValue):
        """
            ABSTRACT: Updates the specified assessment using
            the provided HTTP REQUEST formValue.

            NOTE: This method MUST be overriden in the implementing
            class to suitably update the component state
        """
        
        Component.updateAssessmentState(self, assessment, formValue)
        value = formValue
        
        #get the current assessment state for the component from the assessment
        state = assessment.getComponentState(self.getIdentity())

        #update the current state
        state.setValue(value)

        #update the assessment
        assessment.setIsModified(true)

    #---------------------------------------------------------------#
    # END: Assessible Implementation                                #
    #---------------------------------------------------------------#
    
    #---------------------------------------------------------------#
    # BEGIN: KERLExtensions Implementation                          #
    #---------------------------------------------------------------#

    def compileKERLtoJavaScript(self, expression):
        """
            Returns a Python string that represents a JavaScript
            expression, which may be used to evaluate the status
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.
            
            RETURN TYPE: String         
        """
        
        
        #example: cond="['ec','selected', 'id1']" 
        #returns 'document.getElementById("ec").value=='id1'

        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #determine the operator we are going to process
        opr = expression[1]
        #-------------------------------
        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":

            return "document.getElementById(\"%s\").value == \"%s\"" % (identity, asJavaScript(expression[2]))
            
        #-------------------------------
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":

            return "document.getElementById(\"%s\").value != \"%s\"" % (identity, asJavaScript(expression[2]))
        
        #-------------------------------
        else:
            return "true"

    #---------------------------------------------------------------#

    def compileKERLtoEnglish(self, expression):
        """
            Returns a string representing an 'English' version
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            English descriptions of KERL expressions that may be 
            used to describe things like AssessmentFilters
            
            RETURN TYPE: String         
        """
        
        #determine the operator we are going to process
        operator = expression[1]
        
        #-------------------------------
        #the entity is 'selected' 
        if operator == "selected":

            return "%s '%s' %s is selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntitySource().getEntity(expression[2]).getTitle())
            
        #-------------------------------
        #the entity is 'unselected' 
        elif operator == "unselected":

            return "%s '%s' %s is not selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntitySource().getEntity(expression[2]).getTitle())
         
        #-------------------------------
        else:
            return ""

    #---------------------------------------------------------------#

    def evaluate(self, expression, assessment):
        """
            Evalutes the component class specific KERL expression
            using the provided assessment.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.

            RETURN TYPE: Boolean         
        """
        
        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #get the stored state
        state = assessment.getComponentState(identity)

        #determine the operator we are going to process
        opr = expression[1]
        #-------------------------------
        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":

            return state.getValue() == expression[2]
            
        #-------------------------------
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":

            return state.getValue() != expression[2]
        
        #-------------------------------
        else:
            return true
    
    #---------------------------------------------------------------#

    def getDependencies(self, expression):
        """
            Returns a list of components that are required to
            evaluate the component class specific KERL expression.

            NOTE: Includes the specific component itself.

            RETURN TYPE: {Component} indexed by Component.getIdentity()
       """

        return {self.getIdentity():self}
    
    #---------------------------------------------------------------#

    def updateKERLIdentities(self, expression, identityMappings):
        """
            Returns the expression with the appropriate old identities
            (as identified by the identityMappings) replaced with 
            new identities.
        """
        
        #get the component identity from the expression (first element)
        identity = expression[0]
        
        #is the component in the identityMappings?
        if identityMappings.has_key(identity):
            expression[0] = identityMappings[identity]
        
        return expression

    #---------------------------------------------------------------#
    # END: KERLExtensions Implementation                            #
    #---------------------------------------------------------------#

    def getAssessmentWizardAndWizardState(self, assessment, wizardName):
        """
            ABSTRACT: Returns a tuple containing a Wizard instance and
            an associated WizardState instance (Wizard, WizardState)
            for the specified assessment and wizardName.
            
            This method is used to initialise a named assessment wizard
            that will be used during the assessment process on behalf
            of this component.
            
            A value of (None, None) is returned if there is no wizard
            available for the specified wizardName. (or assessment 
            wizards are not supported for the component)
            
            RETURN TYPE: (Wizard, WizardState)
        """ 

        #construct an 'EntityFinder' wizard?
        if wizardName == 'EntityFinder':
            #define the steps
            step1 = WizardStepDefinition("%s Finder" % self.getEntitySource().getEntityNameSingular(), 
                                         "Choose one %s from the list of %s below" % (self.getEntitySource().getEntityNameSingular(), self.getEntitySource().getEntityNamePlural()))
            
            #a list of entities to choose from
            entityMappingList = [('(none)','')] + [(entity.getTitle(), entity.getIdentity()) for entity in self.getEntitySource().getEntities()]
            step1.addPropertyDefinition(
                PropertyDefinition(
                    identity="entityIdentity",
                    displayName="Available %s:" % self.getEntitySource().getEntityNamePlural(),
                    defaultValue='',
                    description="",
                    propertyType=pt_STRING,
                    propertyStructure=ps_ATOMIC,
                    propertyFormat=pf_NATIVE,
                    hasEnumeratedValues=true,
                    enumeratedValuesMapping=entityMappingList,
                    size=10
                )
            )
            
            #define the wizard
            wizard = SimpleWizard([step1])
            
            #initialise the wizard state (with initial selections)
            wizardState = WizardState()
            wizardState.setPropertyValue("entityIdentity", assessment.getComponentState(self.getIdentity()).getValue())
            
            #return what we've created so that we can create a wizard with it
            return (wizard, wizardState)
        
        else:
            #unknown wizard requested
            return (None,None)

    #---------------------------------------------------------------#

    def executeAssessmentWizard(self, assessment, wizardName, wizard, wizardState):
        """
            ABSTRACT: Executes the specified assessment wizard with
            the provided wizard state.
            
            Override this method to implement assessment wizard processing
            
            RETURN TYPE: None
        """

        #handle the EnityFinder
        if wizardName == 'EntityFinder':

            #get the existing component state
            state = assessment.getComponentState(self.getIdentity())

            #set the component state to the select entity identity
            state.setValue(wizardState.getPropertyValue('entityIdentity'))

    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

# Register Component Information with the Knowledge Engine
registerGenericObjectClass(EntityChooser)

#####################################################################
